/******************************************************************************
 * RLT DSP
 * by Roberto Bender <beto.bender@gmail.com>
 * under GPL 2.0 license
 * 2009
******************************************************************************/

//-----------------------------------------------------------------------------
// System Includes 
//-----------------------------------------------------------------------------
#include <GL/glut.h>
#include <math.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>

#ifdef USE_SEMAPHORE
#include <semaphore.h>
#endif

//-----------------------------------------------------------------------------
// Custom Includes
//-----------------------------------------------------------------------------
#include "filters.h"
#include "nilfilter.h"
#include "distortion.h"
#include "delay.h"
#include "harmonic.h"
#include "defines.h"
#include "gain.h"

short vinput_sample[READ_TIMES][BUFFER_SIZE] = {0};
short voutput_sample[BUFFER_SIZE] = {0};

//-----------------------------------------------------------------------------
// Globals Definitions
//-----------------------------------------------------------------------------
int fd;
int sampling_frequency = SAMPLE_FREQUENCY;
int channels = 0;
int format = AFMT_S16_NE;
int frag = 0x7fff000a;        /* Unlimited number of 1k fragments */
int current_reading_buffer = 0;
int current_writing_buffer = 0;

#ifndef USE_SEMAPHORE
int event = 1;
#else
sem_t iosem_reading;
sem_t iosem_writing;
#endif

extern inputWord m_Silence;

//-----------------------------------------------------------------------------
// Reading Thread
//-----------------------------------------------------------------------------
void * reading_thread(void * ptr)
{
    while(1)
    {
#ifdef USE_SEMAPHORE
        sem_wait(&iosem_reading);
#endif
        read(fd,vinput_sample[current_reading_buffer], BUFFER_SIZE*BUFFER_WORD);
        current_reading_buffer = (current_reading_buffer + 1) % READ_TIMES;

#ifndef USE_SEMAPHORE
        while(!event) 
            usleep(100);
        event = 0;
#else
        sem_post(&iosem_writing);
#endif
    }

    return NULL;
}

//-----------------------------------------------------------------------------
// Recalculate Silence
//-----------------------------------------------------------------------------
int recalculate_silence = 1;
void silenceCalc()
{
    int i = 0;
    long int sum = 0;
    
    printf("Recalculating Silence...\n");

    for(i = 0; i < BUFFER_SIZE; ++i)
        sum += vinput_sample[current_writing_buffer][i];
    m_Silence = sum / BUFFER_SIZE;
    recalculate_silence = 0;
    
    printf("Silence Recalculated at: %d\n", m_Silence);

}
//-----------------------------------------------------------------------------
// Writing Thread
//-----------------------------------------------------------------------------
int pause_sound = 0;
void * writing_thread(void * ptr)
{
    while(1)
    {
        int i = 0;
#ifndef USE_SEMAPHORE
        while(event)
            usleep(100);
#else
        sem_wait(&iosem_writing);
#endif
        if(!pause_sound)
        {
            if(recalculate_silence) silenceCalc();
            for(; i < BUFFER_SIZE; ++i) voutput_sample[i] = CalcFilter(vinput_sample[current_writing_buffer][i]);
            write(fd, voutput_sample, BUFFER_SIZE*BUFFER_WORD);
            current_writing_buffer = (current_writing_buffer + 1) % READ_TIMES;
        }

#ifndef USE_SEMAPHORE
        event = 1;
#else
        sem_post(&iosem_reading); 
#endif
    }
    return NULL;
}

//-----------------------------------------------------------------------------
// Open GL Render Scene
//-----------------------------------------------------------------------------
void renderScene(void) 
{
    short i = 0;
    //static int min = 0, max = 0;
	glClear(GL_COLOR_BUFFER_BIT);

    for(i = 0; i < BUFFER_SIZE - 1; ++i)
    {
#ifdef DRAW_BOXES
    	glBegin(GL_LINES);
	        glColor3f(0, 1, 0);
		    glVertex3d(i,voutput_sample[i]-m_Silence,0);
		    glVertex3d(i+1,voutput_sample[i+1]-m_Silence,0);
    	glEnd();
#else
        glBegin(GL_POLYGON);
            glColor3f(0, 1, 0);
            glVertex3d(i, 0, 0);
            glVertex3d(i, (short)voutput_sample[i], 0);
            glVertex3d(i + 1, (short)voutput_sample[i + 1], 0);
            glVertex3d(i + 1, 0, 0);
        glEnd();

        //if(voutput_sample[i] > max) { max = voutput_sample[i]; printf("max: %d\n", max); }
        //if(voutput_sample[i] < min) { min = voutput_sample[i]; printf("min: %d\n", min); }

    	/*glBegin(GL_LINES);
	        glColor3f(1, 0, 0);
		    glVertex3d(i,vinput_sample[current_writing_buffer][i]-m_Silence,0);
		    glVertex3d(i+1,vinput_sample[current_writing_buffer][i+1]-m_Silence,0);
    	glEnd();
        */
#endif
    }
   
	glutSwapBuffers();
}

//-----------------------------------------------------------------------------
// Keyboard Reading Function
//-----------------------------------------------------------------------------
void keyboard (unsigned char key, int x, int y) 
{
    static int adding = 0;
    
    switch(key)
    {
        case 'h':
            printf("Use: \n");
            printf("|-+H For This Help Message\n");
            printf("|-+S Recalculate Silence\n");
            printf("|-+P Pause\n");
            printf("|-+E Enable Filters\n");
            printf("|-+R Disable Filters\n");
            printf("|-+C Clear Filters (remove all added filters)\n");
            printf("|-+F Add Filter\n");
            printf("|-+-+L Delay Filter\n");
            printf("|-+-+D Distortion Filter\n");
            printf("|-+-+-+Q Raise Distortion Amp Crop by 1000Hz\n");
            printf("|-+-+-+W Lower Distortion Amp Crop by 1000Hz\n");
            printf("|-+-+G Gain Filter\n");
            printf("|-+-+-+1 Raise Gain Amp by 0.1\n");
            printf("|-+-+-+2 Raise Gain Amp by 0.1\n");
            break;
        case 's':
            recalculate_silence = 1;
            break;
        case 'p':
            pause_sound = (pause_sound + 1)%2;
            break;
        case 'e':
            EnableFilters();
            printf("Filter Enabled\n");
            break;
        case 'r':
            DisableFilters();
            printf("Filter Disabled(output = input)\n");
            break;
        case 'q':
            defaultDistortionParam.m_AmpCrop += 1000;
            printf("Current Distotion Amp Crop: %f\n", defaultDistortionParam.m_AmpCrop);
            break;
        case 'w':
            defaultDistortionParam.m_AmpCrop -= 1000;
            printf("Current Distotion Amp Crop: %f\n", defaultDistortionParam.m_AmpCrop);
            break;
        case '1':
            defaultGainParam.m_Amp += 0.1;
            printf("Current Gain Amp: %f\n", defaultGainParam.m_Amp);
            break;
        case '2':
            defaultGainParam.m_Amp -= 0.1;
            printf("Current Gain Amp: %f\n", defaultGainParam.m_Amp);
            break;
        case 'c':
            ClearFilters();
            printf("Filters Cleared (output = input)\n");
            break;
        case 'f':
            if(adding)
            {
                adding = 0;
                printf("Adding Filter Disabled\n");
            }
            else
            {
                adding = 1;
                printf("Adding Filter Enabled... please choose a filter\n");
            }
            break;
        case 'd':
            if(adding)
            {
                int rc = 0;
                if(!(rc = AddFilter(&defaultDistortionParam, distortion)))
                {
                    printf("Disortion Filter Added\n");
                    adding = 0;
                }
                else
                {
                    printf("Fail to add filter: %d\n", rc);
                }
            }
            break;
        case 'g':
            if(adding)
            {
                int rc = 0;
                if(!(rc = AddFilter(&defaultGainParam, gain)))
                {
                    printf("Gain Filter Added\n");
                    adding = 0;
                }
                else
                {
                    printf("Fail to add filter: %d\n", rc);
                }
            }
            break;
        case 'l':
            if(adding)
            {
                int rc = 0;
                if(!(rc = AddFilter(&defaultDelayParam, delay)))
                {
                    printf("Delay Filter Added\n");
                    adding = 0;
                }
                else
                {
                    printf("Fail to add filter: %d\n", rc);
                }
            }
            break;

        default:
            break;
    }
}

//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
int main(int argc, char* argv[])
{
    printf("Application Started\n");
    srandom(time(NULL));
    printf("Randon Seed Initialized\n");

    // Initialize DSP //
    fd = open("/dev/dsp",O_RDWR);

    if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels)==-1) 
    {
        perror("bad channels setting.");
        return(2); 
    }

    if (ioctl(fd, SNDCTL_DSP_SETFMT, &format)==-1) 
    {
        perror("bad sound format.");
        return(2); 
    } 

    if (ioctl(fd, SNDCTL_DSP_SPEED, &sampling_frequency) == -1) 
    {
        perror("bad sampling rate.");
        return(2); 
    }

    if (ioctl(fd, SNDCTL_DSP_SETDUPLEX, NULL) == -1) 
    {
        perror("not full duplex.");
        return(2); 
    }

    if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &frag) == -1)
    {
        perror ("dsp fragment not suported");
        return(2);
    }

    // Initialize Menu
    printf("*** Use: H for Help Menu ***\n");

    // Initialize Filters //
    if(StartFiltersEngine()) return 2;
    printf("Filter Engine Initialized\n");
    defaultDistortionParam.m_AmpCrop = 20000.00;
    printf("Distotion Filter Initialized With %f\n", defaultDistortionParam.m_AmpCrop);
    defaultGainParam.m_Amp = 1.0;
    printf("Gain Filter Initialized With %f\n", defaultGainParam.m_Amp);
    printf("Delay Filter Initialized \n");
    
    // Initialize IO Threads //
#ifdef USE_SEMAPHORE
    sem_init(&iosem_reading, 0, READ_TIMES);
    sem_init(&iosem_writing, 0, 0);
#endif
    pthread_t thread1, thread2;
    pthread_create(&thread1, NULL, reading_thread, NULL);
    printf("Reading Thread Created\n");
    pthread_create(&thread2, NULL, writing_thread, NULL);
    printf("Writing Thread Created\n");
    sem_post(&iosem_reading);

    // Initialize GL //
    glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
	glutInitWindowSize(400,200);
	glutCreateWindow("Signal Output");
	gluOrtho2D(0, BUFFER_SIZE, WORD_MIN, WORD_MAX);

    glutIdleFunc(renderScene);
    glutKeyboardFunc(keyboard);
	glutMainLoop();

    return 0;
}
